Merge "Add clarification to wfUseMW() phpdoc"
authorjenkins-bot <jenkins-bot@gerrit.wikimedia.org>
Thu, 31 Oct 2013 19:21:38 +0000 (19:21 +0000)
committerGerrit Code Review <gerrit@wikimedia.org>
Thu, 31 Oct 2013 19:21:38 +0000 (19:21 +0000)
1  2 
includes/GlobalFunctions.php

@@@ -2710,9 -2710,9 +2710,9 @@@ function wfShellExecDisabled() 
                        $functions = explode( ',', ini_get( 'disable_functions' ) );
                        $functions = array_map( 'trim', $functions );
                        $functions = array_map( 'strtolower', $functions );
 -                      if ( in_array( 'passthru', $functions ) ) {
 -                              wfDebug( "passthru is in disabled_functions\n" );
 -                              $disabled = 'passthru';
 +                      if ( in_array( 'proc_open', $functions ) ) {
 +                              wfDebug( "proc_open is in disabled_functions\n" );
 +                              $disabled = 'disabled';
                        }
                }
        }
   * configuration if supported.
   * @param string $cmd Command line, properly escaped for shell.
   * @param &$retval null|Mixed optional, will receive the program's exit code.
 - *                 (non-zero is usually failure)
 + *                 (non-zero is usually failure). If there is an error from
 + *                 read, select, or proc_open(), this will be set to -1.
   * @param array $environ optional environment variables which should be
   *                 added to the executed command environment.
   * @param array $limits optional array with limits(filesize, memory, time, walltime)
   *                 this overwrites the global wgShellMax* limits.
 - * @param array $options Array of options. Only one is "duplicateStderr" => true, which
 - *                 Which duplicates stderr to stdout, including errors from limit.sh
 + * @param array $options Array of options:
 + *    - duplicateStderr: Set this to true to duplicate stderr to stdout, 
 + *      including errors from limit.sh
 + *      
   * @return string collected stdout as a string
   */
  function wfShellExec( $cmd, &$retval = null, $environ = array(), $limits = array(), $options = array() ) {
                $retval = 1;
                return $disabled == 'safemode' ?
                        'Unable to run external programs in safe mode.' :
 -                      'Unable to run external programs, passthru() is disabled.';
 +                      'Unable to run external programs, proc_open() is disabled.';
        }
  
        $includeStderr = isset( $options['duplicateStderr'] ) && $options['duplicateStderr'];
        }
        $cmd = $envcmd . $cmd;
  
 +      $useLogPipe = false;
        if ( php_uname( 's' ) == 'Linux' ) {
                $time = intval ( isset( $limits['time'] ) ? $limits['time'] : $wgMaxShellTime );
                if ( isset( $limits['walltime'] ) ) {
                                        'MW_CGROUP=' . escapeshellarg( $wgShellCgroup ) . '; ' .
                                        "MW_MEM_LIMIT=$mem; " .
                                        "MW_FILE_SIZE_LIMIT=$filesize; " .
 -                                      "MW_WALL_CLOCK_LIMIT=$wallTime"
 +                                      "MW_WALL_CLOCK_LIMIT=$wallTime; " .
 +                                      "MW_USE_LOG_PIPE=yes"
                                );
 +                      $useLogPipe = true;
                } elseif ( $includeStderr ) {
                        $cmd .= ' 2>&1';
                }
        }
        wfDebug( "wfShellExec: $cmd\n" );
  
 -      // Default to an unusual value that shouldn't happen naturally,
 -      // so in the unlikely event of a weird php bug, it would be
 -      // more obvious what happened.
 -      $retval = 200;
 -      ob_start();
 -      passthru( $cmd, $retval );
 -      $output = ob_get_contents();
 -      ob_end_clean();
 +      $desc = array(
 +              0 => array( 'file', 'php://stdin', 'r' ),
 +              1 => array( 'pipe', 'w' ),
 +              2 => array( 'file', 'php://stderr', 'w' ) );
 +      if ( $useLogPipe ) {
 +              $desc[3] = array( 'pipe', 'w' );
 +      }
 +      $pipes = null;
 +      $proc = proc_open( $cmd, $desc, $pipes );
 +      if ( !$proc ) {
 +              wfDebugLog( 'exec', "proc_open() failed: $cmd\n" );
 +              $retval = -1;
 +              return '';
 +      }
 +      $outBuffer = $logBuffer = '';
 +      $emptyArray = array();
 +      $status = false;
 +      $logMsg = false;
 +
 +      // According to the documentation, it is possible for stream_select()
 +      // to fail due to EINTR. I haven't managed to induce this in testing 
 +      // despite sending various signals. If it did happen, the error 
 +      // message would take the form: 
 +      //
 +      // stream_select(): unable to select [4]: Interrupted system call (max_fd=5)
 +      //
 +      // where [4] is the value of the macro EINTR and "Interrupted system
 +      // call" is string which according to the Linux manual is "possibly"
 +      // localised according to LC_MESSAGES.
 +      $eintr = defined( 'SOCKET_EINTR' ) ? SOCKET_EINTR : 4;
 +      $eintrMessage = "stream_select(): unable to select [$eintr]";
 +
 +      // Build a table mapping resource IDs to pipe FDs to work around a
 +      // PHP 5.3 issue in which stream_select() does not preserve array keys
 +      // <https://bugs.php.net/bug.php?id=53427>.
 +      $fds = array();
 +      foreach ( $pipes as $fd => $pipe ) {
 +              $fds[(int)$pipe] = $fd;
 +      }
 +
 +      while ( true ) {
 +              $status = proc_get_status( $proc );
 +              if ( !$status['running'] ) {
 +                      break;
 +              }
 +              $status = false;
 +
 +              $readyPipes = $pipes;
  
 -      if ( $retval == 127 ) {
 -              wfDebugLog( 'exec', "Possibly missing executable file: $cmd\n" );
 +              // Clear last error
 +              @trigger_error( '' );
 +              if ( @stream_select( $readyPipes, $emptyArray, $emptyArray, null ) === false ) {
 +                      $error = error_get_last();
 +                      if ( strncmp( $error['message'], $eintrMessage, strlen( $eintrMessage ) ) == 0 ) {
 +                              continue;
 +                      } else {
 +                              trigger_error( $error['message'], E_USER_WARNING );
 +                              $logMsg = $error['message'];
 +                              break;
 +                      }
 +              }
 +              foreach ( $readyPipes as $pipe ) {
 +                      $block = fread( $pipe, 65536 );
 +                      $fd = $fds[(int)$pipe];
 +                      if ( $block === '' ) {
 +                              // End of file
 +                              fclose( $pipes[$fd] );
 +                              unset( $pipes[$fd] );
 +                              if ( !$pipes ) {
 +                                      break 2;
 +                              }
 +                      } elseif ( $block === false ) {
 +                              // Read error
 +                              $logMsg = "Error reading from pipe";
 +                              break 2;
 +                      } elseif ( $fd == 1 ) {
 +                              // From stdout
 +                              $outBuffer .= $block;
 +                      } elseif ( $fd == 3 ) {
 +                              // From log FD
 +                              $logBuffer .= $block;
 +                              if ( strpos( $block, "\n" ) !== false ) {
 +                                      $lines = explode( "\n", $logBuffer );
 +                                      $logBuffer = array_pop( $lines );
 +                                      foreach ( $lines as $line ) {
 +                                              wfDebugLog( 'exec', $line );
 +                                      }
 +                              }
 +                      }
 +              }
        }
 -      return $output;
 +
 +      foreach ( $pipes as $pipe ) {
 +              fclose( $pipe );
 +      }
 +
 +      // Use the status previously collected if possible, since proc_get_status()
 +      // just calls waitpid() which will not return anything useful the second time.
 +      if ( $status === false ) {
 +              $status = proc_get_status( $proc );
 +      }
 +
 +      if ( $logMsg !== false ) {
 +              // Read/select error
 +              $retval = -1;
 +              proc_close( $proc );
 +      } elseif ( $status['signaled'] ) {
 +              $logMsg = "Exited with signal {$status['termsig']}";
 +              $retval = 128 + $status['termsig'];
 +              proc_close( $proc );
 +      } else {
 +              if ( $status['running'] ) {
 +                      $retval = proc_close( $proc );
 +              } else {
 +                      $retval = $status['exitcode'];
 +                      proc_close( $proc );
 +              }
 +              if ( $retval == 127 ) {
 +                      $logMsg = "Possibly missing executable file";
 +              } elseif ( $retval >= 129 && $retval <= 192 ) {
 +                      $logMsg = "Probably exited with signal " . ( $retval - 128 );
 +              }
 +      }
 +
 +      if ( $logMsg !== false ) {
 +              wfDebugLog( 'exec', "$logMsg: $cmd\n" );
 +      }
 +
 +      return $outBuffer;
  }
  
  /**
@@@ -3187,6 -3065,14 +3187,14 @@@ function wfUsePHP( $req_ver ) 
   * This is useful for extensions which due to their nature are not kept in sync
   * with releases
   *
+  * Note: Due to the behavior of PHP's version_compare() which is used in this
+  * fuction, if you want to allow the 'wmf' development versions add a 'c' (or
+  * any single letter other than 'a', 'b' or 'p') as a post-fix to your
+  * targeted version number. For example if you wanted to allow any variation
+  * of 1.22 use `wfUseMW( '1.22c' )`. Using an 'a' or 'b' instead of 'c' will
+  * not result in the same comparison due to the internal logic of
+  * version_compare().
+  *
   * @see perldoc -f use
   *
   * @param $req_ver Mixed: the version to check, can be a string, an integer, or